package com.qall.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;

import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JSONUtil {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final ObjectMapper LOG_OBJ_MAPPER = new ObjectMapper();
    private static final Logger LOG = LoggerFactory.getLogger(JSONUtil.class);

    static {

        genericConfig(OBJECT_MAPPER);

        registerJavaTime(OBJECT_MAPPER);


        LOG_OBJ_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

        LOG_OBJ_MAPPER.configure(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS.mappedFeature(), true);

        LOG_OBJ_MAPPER.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        registerJavaTime(LOG_OBJ_MAPPER);
    }


    public static void genericConfig(ObjectMapper om) {

        om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        om.configure(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS.mappedFeature(), true);

        om.configure(JsonParser.Feature.ALLOW_COMMENTS, true);

        om.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);

        om.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

        om.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);

        om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        om.setTimeZone(TimeZone.getTimeZone("GMT+8"));
    }


    public static void registerJavaTime(ObjectMapper om) {

        JavaTimeModule javaTimeModule = new JavaTimeModule();

        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        DateTimeFormatter localTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");

        DateTimeFormatter localDateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));

        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(localTimeFormatter));

        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(localDateFormatter));

        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));

        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(localTimeFormatter));

        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(localDateFormatter));

        om.registerModule(javaTimeModule);
    }


    public static void addModule(Module module) {

        LOG_OBJ_MAPPER.registerModule(module);

        OBJECT_MAPPER.registerModule(module);
    }


    public static void addLogModule(Module module) {

        LOG_OBJ_MAPPER.registerModule(module);
    }


    public static void setSerializationInclusion(JsonInclude.Include include) {

        OBJECT_MAPPER.setSerializationInclusion(include);
    }


    public static String toJson(Object obj) {

        return toJson(obj, false);
    }


    public static String toJson(Object obj, boolean pretty) {
        try {

            if (pretty) {

                return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
            }


            return OBJECT_MAPPER.writeValueAsString(obj);

        } catch (Exception e) {


            LOG.error("toJson fail: ", e);

            return "";
        }
    }


    public static String toJson(ObjectMapper ob, Object obj, boolean pretty) {
        try {

            if (pretty) {

                return ob.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
            }


            return ob.writeValueAsString(obj);

        } catch (Exception e) {


            LOG.error("toJson fail: ", e);

            return "";
        }
    }


    public static <T> T fromJson(String jsonStr, Class<T> clz) {
        try {

            return (T) OBJECT_MAPPER.readValue(jsonStr, clz);
        } catch (Exception e) {


            LOG.error("fromJson fail: ", e);

            return null;
        }
    }


    public static Map<String, Object> jsonToMap(String jsonStr) {
        try {

            JavaType jvt = OBJECT_MAPPER.getTypeFactory().constructParametricType(HashMap.class, new Class[]{String.class, Object.class});

            return  OBJECT_MAPPER.readValue(jsonStr, jvt);
        } catch (Exception e) {


            LOG.error("jsonToMap fail: ", e);

            return new HashMap<>();
        }
    }


    public static <T> List<T> jsonToList(String jsonStr, Class<T> clz) {
        try {

            JavaType jt = OBJECT_MAPPER.getTypeFactory().constructParametricType(ArrayList.class, new Class[]{clz});

            return (List<T>) OBJECT_MAPPER.readValue(jsonStr, jt);
        } catch (IOException e) {


            LOG.error("jsonToList fail: ", e);

            return new ArrayList<>();
        }
    }


    public static String log(Object obj) {

        if (null == obj) {

            return "NULL";
        }


        try {

            return LOG_OBJ_MAPPER.writeValueAsString(obj);
        } catch (Exception e) {


            LOG.error("log fail: ", e);

            return "";
        }
    }

    public static String makeLog(Object obj) {
        String body;

        if (null == obj) {

            return "NULL";
        }


        try {

            body = LOG_OBJ_MAPPER.writeValueAsString(obj);
        } catch (Exception e) {


            LOG.error("makeLog fail: ", e);

            body = "";
        }

        return obj.getClass().getSimpleName() + ": " + body;
    }


    static class PwdFilterSerializer
            extends JsonSerializer<String> {
        public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {

            String name = jsonGenerator.getOutputContext().getCurrentName();

            if (null != name && canFilter(name)) {


                jsonGenerator.writeString("******");
            } else {


                jsonGenerator.writeString(s);
            }
        }


        private boolean canFilter(String name) {

            return ("token".equals(name) || "pPWD".equals(name) || "password"
                    .equalsIgnoreCase(name) || name.endsWith("Password") || "passwd"
                    .equalsIgnoreCase(name) || name.endsWith("Passwd"));
        }
    }
}


